package com.wkh.project.map.service.impl;

import com.wkh.project.common.result.ResultCodeEnum;
import com.wkh.project.common.util.LocationUtil;
import com.wkh.project.driver.client.DriverInfoFeignClient;
import com.wkh.project.map.repository.OrderServiceLocationRepository;
import com.wkh.project.map.service.LocationService;
import com.wkh.project.model.entity.driver.DriverSet;
import com.wkh.project.model.entity.map.OrderServiceLocation;
import com.wkh.project.model.form.map.OrderServiceLocationForm;
import com.wkh.project.model.form.map.SearchNearByDriverForm;
import com.wkh.project.model.form.map.UpdateDriverLocationForm;
import com.wkh.project.model.form.map.UpdateOrderLocationForm;
import com.wkh.project.model.vo.map.NearByDriverVo;
import com.wkh.project.model.vo.map.OrderLocationVo;
import com.wkh.project.model.vo.map.OrderServiceLastLocationVo;
import com.wkh.project.order.client.OrderInfoFeignClient;
import com.wkh.project.service.constant.RedisConstant;
import com.wkh.project.service.constant.SystemConstant;
import com.wkh.project.service.execption.GuiguException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.connection.ReactiveGeoCommands;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class LocationServiceImpl implements LocationService {
    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private DriverInfoFeignClient driverInfoFeignClient;

    @Resource
    private OrderServiceLocationRepository orderServiceLocationRepository;
    @Resource
    private MongoTemplate mongoTemplate;
    @Autowired
    private OrderInfoFeignClient orderInfoFeignClient;

    @Override
    public Boolean updateDriverLocation(UpdateDriverLocationForm updateDriverLocationForm) {
        /**
         *  Redis GEO 主要用于存储地理位置信息，并对存储的信息进行相关操作，该功能在 Redis 3.2 版本新增。
         *  后续用在，乘客下单后寻找5公里范围内开启接单服务的司机，通过Redis GEO进行计算
         */
        Point point = new Point(updateDriverLocationForm.getLongitude().doubleValue(), updateDriverLocationForm.getLatitude().doubleValue());
        redisTemplate.opsForGeo().add(RedisConstant.DRIVER_GEO_LOCATION, point, updateDriverLocationForm.getDriverId().toString());
        return true;
    }

    @Override
    public Boolean removeDriverLocation(Long driverId) {
        redisTemplate.opsForGeo().remove(RedisConstant.DRIVER_GEO_LOCATION, driverId.toString());
        return true;
    }

    @Override
    public List<NearByDriverVo> searchNearByDriver(SearchNearByDriverForm searchNearByDriverForm) {
        ArrayList<NearByDriverVo> nearByDriverVos = new ArrayList<>();
        //创建Circle对象 设置经纬度
        Point point = new Point(searchNearByDriverForm.getLongitude().doubleValue(), searchNearByDriverForm.getLatitude().doubleValue());
        //定义距离为5公里
        Distance distance = new Distance(SystemConstant.NEARBY_DRIVER_RADIUS, RedisGeoCommands.DistanceUnit.KILOMETERS);
        Circle circle = new Circle(point,distance);
        //定义Geo参数，设置返回结果包含的内容
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs()
                .includeDistance() //包含距离
                .includeCoordinates() //包含坐标
                .sortAscending();//升序排列
        //操作redis中的geo数据类型
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().radius(RedisConstant.DRIVER_GEO_LOCATION, circle, args);
        //得到一个此范围内的可接单的司机集合
        if (results == null){
            return nearByDriverVos;
        }
        List<GeoResult<RedisGeoCommands.GeoLocation<String>>> resultsContent = results.getContent();
        for (GeoResult<RedisGeoCommands.GeoLocation<String>> item : resultsContent) {
            //司机id
            Long driverId = Long.parseLong(item.getContent().getName());
            //当前距离
            BigDecimal currentDistance = BigDecimal.valueOf(item.getDistance().getValue()).setScale(2, RoundingMode.HALF_UP);
            log.info("司机：{}，距离：{}", driverId, item.getDistance().getValue());

            //获取司机接单设置参数
            DriverSet driverSet = driverInfoFeignClient.getDriverSet(driverId).getData();
            //接单里程判断，acceptDistance==0：不限制，
            if (driverSet.getAcceptDistance().doubleValue() != 0 && driverSet.getAcceptDistance().subtract(currentDistance).doubleValue() < 0) {
                continue;
            }
            //订单里程判断，orderDistance==0：不限制
            if (driverSet.getOrderDistance().doubleValue() != 0 && driverSet.getOrderDistance().subtract(searchNearByDriverForm.getMileageDistance()).doubleValue() < 0) {
                continue;
            }

            //满足条件的附近司机信息
            NearByDriverVo nearByDriverVo = new NearByDriverVo();
            nearByDriverVo.setDriverId(driverId);
            nearByDriverVo.setDistance(currentDistance);
            //加入集合
            nearByDriverVos.add(nearByDriverVo);
        }
        return nearByDriverVos;
    }

    @Override
    public Boolean updateOrderLocationToCache(UpdateOrderLocationForm updateOrderLocationForm) {
        OrderLocationVo orderLocationVo = new OrderLocationVo();
        orderLocationVo.setLongitude(updateOrderLocationForm.getLongitude());
        orderLocationVo.setLatitude(updateOrderLocationForm.getLatitude());
        redisTemplate.opsForValue().set(RedisConstant.UPDATE_ORDER_LOCATION + updateOrderLocationForm.getOrderId(), orderLocationVo);
        return true;
    }

    @Override
    public OrderLocationVo getCacheOrderLocation(Long orderId) {
        return (OrderLocationVo)redisTemplate.opsForValue().get(RedisConstant.UPDATE_ORDER_LOCATION + orderId);
    }

    @Override
    public Boolean saveOrderServiceLocation(List<OrderServiceLocationForm> orderLocationServiceFormList) {
        List<OrderServiceLocation> list = new ArrayList<>();
        orderLocationServiceFormList.forEach(item -> {
            OrderServiceLocation orderServiceLocation = new OrderServiceLocation();
            BeanUtils.copyProperties(item, orderServiceLocation);
            orderServiceLocation.setId(ObjectId.get().toString());
            orderServiceLocation.setCreateTime(new Date());
            list.add(orderServiceLocation);
        });
        orderServiceLocationRepository.saveAll(list);
        return true;
    }

    @Override
    public OrderServiceLastLocationVo getOrderServiceLastLocation(Long orderId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("orderId").is(orderId));
        query.with(Sort.by(Sort.Order.desc("createTime")));
        query.limit(1);
        OrderServiceLocation orderServiceLocation = mongoTemplate.findOne(query, OrderServiceLocation.class);

        //封装返回对象
        OrderServiceLastLocationVo orderServiceLastLocationVo = new OrderServiceLastLocationVo();
        BeanUtils.copyProperties(orderServiceLocation, orderServiceLastLocationVo);
        return orderServiceLastLocationVo;
    }

    @Override
    public BigDecimal calculateOrderRealDistance(Long orderId) {
        //1.在mongodb中查询所有属于该订单的位置信息
        Query query = new Query();
        query.addCriteria(new Criteria().where("orderId").is(orderId))
                .with(Sort.by(Sort.Direction.ASC));
        List<OrderServiceLocation> list = mongoTemplate.find(
                query, OrderServiceLocation.class);
        //2.实际距离的计算
        double realDistance = 0;
        if (!CollectionUtils.isEmpty(list)){
            for (int i = 0; i < list.size()-1; i++) {
                OrderServiceLocation location1 = list.get(i);
                OrderServiceLocation location2 = list.get(i+1);
                double distance = LocationUtil.getDistance(location1.getLatitude().doubleValue(),
                        location1.getLongitude().doubleValue(),
                        location2.getLatitude().doubleValue(), location2.getLongitude().doubleValue());
                realDistance+=distance;
            }
        }
        //todo 为了测试
        if (realDistance == 0){
            return orderInfoFeignClient.getOrderInfo(orderId).getData().getExpectDistance().add(new BigDecimal(5));
        }
        //返回实际距离
        return new BigDecimal(realDistance);
    }

}
